package pl.matt.service.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;

import pl.matt.dao.FrameDao;
import pl.matt.dao.FrameRegionDao;
import pl.matt.model.Frame;
import pl.matt.model.FrameRegion;
import pl.matt.model.FrameRegionType;
import pl.matt.model.Rectangle;
import pl.matt.service.ExtractorService;
import flanagan.interpolation.CubicSpline;

public abstract class AbstractExtractorService implements ExtractorService {

	// @SuppressWarnings("unused")
	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
			.getLogger(AbstractExtractorService.class);

	protected FrameDao frameDao;
	protected FrameRegionDao frameRegionDao;

	protected abstract FrameRegionType getFrameRegionType();

	@Override
	public void extractFromVideo(String videoName) {
		init();
		// kasuję poprzednie wpisy
		frameRegionDao.deleteByVideoNameAndFrameRegionType(videoName, getFrameRegionType());
		List<Frame> frames = frameDao.findByVideoNameOrderByNumber(videoName);
		for (Frame frame : frames) {
			Collection<Rectangle> rectangles = detectRectangles(frame);
			Collection<FrameRegion> regions = createRegions(frame, rectangles);
			if (CollectionUtils.isNotEmpty(rectangles)) {
				frameRegionDao.create(regions);
			}
		}
		verifyRegions(frames);
		for (Frame frame : frames) {
			frameRegionDao.createOrUpdate(frame.getFrameRegions());
		}
		clean();
	}

	protected void clean() {

	}

	protected void init() {

	}

	/**
	 * @param frame
	 * @return ścieżka do przygotowanego pliku
	 */
	protected String prepareImage(Frame frame) {
		return frame.getFilePath();
	}

	protected Collection<Rectangle> detectRectangles(Frame frame) {
		String path = prepareImage(frame);
		if (path != null) {
			return detectRectangles(path);
		}
		return null;
	}

	protected abstract Collection<Rectangle> detectRectangles(String imageFilePath);

	private Collection<FrameRegion> createRegions(Frame frame, Collection<Rectangle> rectangles) {
		Collection<FrameRegion> out = new ArrayList<FrameRegion>();
		if (CollectionUtils.isNotEmpty(rectangles)) {
			boolean verified = rectangles.size() == 1;
			for (Rectangle rectangle : rectangles) {
				FrameRegion region = new FrameRegion();
				region.setFrame(frame);
				region.setVerified(verified);
				region.setFrameRegionType(getFrameRegionType());
				region.setHeight(rectangle.getHeight());
				region.setWidth(rectangle.getWidth());
				region.setX(getDX(frame) + rectangle.getX());
				region.setY(getDY(frame) + rectangle.getY());
				out.add(region);
			}
		}
		frame.setFrameRegions(out);
		return out;
	}

	protected int getDX(Frame frame) {
		return 0;
	}

	protected int getDY(Frame frame) {
		return 0;
	}

	private void addValues(Frame frame, FrameRegion frameRegion, List<Double> scale, List<Double> valuesX,
			List<Double> valuesY, List<Double> valuesWidth, List<Double> valuesHeight) {
		scale.add((double) frame.getNumber());
		valuesX.add((double) frameRegion.getX());
		valuesY.add((double) frameRegion.getY());
		valuesWidth.add((double) frameRegion.getWidth());
		valuesHeight.add((double) frameRegion.getHeight());
	}

	protected void verifyRegions(List<Frame> frames) {
		// double[] scale = {1, 2, 3, 4};
		List<Double> scale = new ArrayList<Double>(frames.size());
		// double[] valuesX = {1, 2, 2, 1};
		List<Double> valuesX = new ArrayList<Double>(frames.size());
		List<Double> valuesY = new ArrayList<Double>(frames.size());
		List<Double> valuesWidth = new ArrayList<Double>(frames.size());
		List<Double> valuesHeight = new ArrayList<Double>(frames.size());
		FrameRegion lastVerifiedRegion = null;
		Frame lastFrame = null;
		for (Frame frame : frames) {
			FrameRegion fr = frame.getVerifiedRegion();
			lastFrame = frame;
			if (fr != null) {
				addValues(frame, fr, scale, valuesX, valuesY, valuesWidth, valuesHeight);
				lastVerifiedRegion = fr;
			}
		}
		if (lastFrame.getVerifiedRegion() == null) {
			addValues(lastFrame, lastVerifiedRegion, scale, valuesX, valuesY, valuesWidth, valuesHeight);
		}

		CubicSpline xSpline = new CubicSpline(scale, valuesX);
		CubicSpline ySpline = new CubicSpline(scale, valuesY);
		CubicSpline widthSpline = new CubicSpline(scale, valuesWidth);
		CubicSpline heightSpline = new CubicSpline(scale, valuesHeight);

		for (Frame frame : frames) {
			FrameRegion fr = frame.getVerifiedRegion();
			if (fr == null) {
				Collection<FrameRegion> candidates = frame.getFrameRegions();
				if (CollectionUtils.isNotEmpty(candidates)) {
					FrameRegion verified = null;
					int minDistance = Integer.MAX_VALUE;
					for (FrameRegion frameRegion : candidates) {
						int distance = getDistance(frameRegion, xSpline, ySpline, widthSpline, heightSpline);
						if (distance < minDistance) {
							minDistance = distance;
							verified = frameRegion;
						}
						LOG.debug(frameRegion.getId() + " <-- frameRegion.getId()");
						LOG.debug(distance + " <-- distance");
						LOG.debug(frame.getId() + " <-- frame.getId()");
					}
					verified.setVerified(true);
				} else {
					FrameRegion newRegion = new FrameRegion();
					double frameNumber = frame.getNumber();
					newRegion.setRectangle(getCandidate(frameNumber, xSpline, ySpline, widthSpline, heightSpline));
					newRegion.setArtificial(true);
					newRegion.setFrame(frame);
					frame.getFrameRegions().add(newRegion);
				}
			}
		}

	}

	private Rectangle getCandidate(double frameNumber, CubicSpline xSpline, CubicSpline ySpline, CubicSpline widthSpline, CubicSpline heightSpline) {
		int rX = (int) xSpline.interpolate(frameNumber);
		int rY = (int) ySpline.interpolate(frameNumber);
		int rW = (int) widthSpline.interpolate(frameNumber);
		int rH = (int) heightSpline.interpolate(frameNumber);
		Rectangle r = new Rectangle(rX, rY, rW, rH);
		return r;
	}
	
	private int getDistance(FrameRegion frameRegion, CubicSpline xSpline, CubicSpline ySpline, CubicSpline widthSpline, CubicSpline heightSpline) {
		double frameNumber = frameRegion.getFrame().getNumber();	

		Rectangle r = getCandidate(frameNumber, xSpline, ySpline, widthSpline, heightSpline);		
		int distance = frameRegion.getRectangle().getDistance(r);
		LOG.debug(frameRegion.getId() + " <-- frameRegion.getId()");
		LOG.debug(distance + " <-- distance");
		LOG.debug(r + " <-- r  - estimated");
		return distance;
		
	}

	public void setFrameDao(FrameDao frameDao) {
		this.frameDao = frameDao;
	}

	public void setFrameRegionDao(FrameRegionDao frameRegionDao) {
		this.frameRegionDao = frameRegionDao;
	}

}
